Skip to content

refactor: make Comparison follow dict/Mapping protocol#671

Merged
FBumann merged 4 commits intomainfrom
comparison-mapping-ergonomics
Apr 20, 2026
Merged

refactor: make Comparison follow dict/Mapping protocol#671
FBumann merged 4 commits intomainfrom
comparison-mapping-ergonomics

Conversation

@FBumann
Copy link
Copy Markdown
Member

@FBumann FBumann commented Apr 20, 2026

Summary

  • Comparison.__iter__ now yields case names (keys) instead of (name, FlowSystem) pairs, matching the dict/Mapping convention.
  • Added keys(), values(), and items() methods returning the usual dict views.

Breaking change

Code that relied on for name, fs in comp: should switch to for name, fs in comp.items():. A grep of the repo found no internal call sites using the old pattern.

Test plan

  • pytest tests/test_comparison.py::TestComparisonContainerProtocol — 16 passed
  • Confirm CI is green

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added dict-like view methods for names, systems, and name–system pairs for easier access.
  • Refactoring

    • Iteration now yields case names only; use view methods to access paired data.
  • Bug Fixes

    • Stronger input validation with clearer errors for invalid inputs to prevent misuse.
  • Tests

    • Updated tests to reflect iteration change and to verify validation and view behaviors.
  • Chores

    • Adjusted dev environment dependency constraint for compatibility.

Iteration now yields case names instead of (name, FlowSystem) pairs,
matching Python's dict/Mapping convention. Added keys(), values(), and
items() methods for explicit access. Users iterating pairs should
switch to `for name, fs in comp.items():`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 20, 2026

Warning

Rate limit exceeded

@FBumann has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 49 minutes and 35 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 49 minutes and 35 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5d32ea1e-4090-41c7-b0b5-0f15b223e28c

📥 Commits

Reviewing files that changed from the base of the PR and between 18ec614 and 644c44d.

📒 Files selected for processing (1)
  • flixopt/comparison.py
📝 Walkthrough

Walkthrough

The Comparison class now enforces that flow_systems is a list of FlowSystem at runtime, changes __iter__ to yield case name strings only, and adds dict-like view methods keys(), values(), and items(). Tests were updated accordingly. The dev extras in pyproject.toml constrain xarray<2026.3.

Changes

Cohort / File(s) Summary
Comparison class
flixopt/comparison.py
Added runtime type checks for flow_systems (must be list of FlowSystem); changed __iter__ to yield str case names; added keys(), values(), items() returning proper view types; updated TYPE_CHECKING imports and added self._systems: list[FlowSystem].
Tests
tests/test_comparison.py
Added tests asserting constructor rejects non-list and non-FlowSystem elements; replaced iteration test with test_iter_yields_names; added tests for keys(), values(), and adjusted items() expectations.
Dev deps
pyproject.toml
Constrained dev optional dependency: added xarray<2026.3 to [project.optional-dependencies].dev (temporary compatibility constraint/TODO).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • Feature/minor improvements #556 — Modifies Comparison (adds inputs property and caching); strong overlap in Comparison responsibilities and potential merge interactions.

Poem

🐰 I hopped through code with tiny paws,

Names now spring from Comparison's laws,
Keys and values in tidy queues,
Systems checked for proper shoes,
A carrot-coded change — hooray, good news! 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description covers the summary of changes and breaking change well. However, it does not follow the provided template structure with required sections like Type of Change, Related Issues, Testing checklist, and the full Checklist. Follow the repository's PR description template: add Type of Change checkboxes, Related Issues section, complete Testing section, and full Checklist with all items properly marked.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and accurately summarizes the main change: refactoring Comparison to follow dict/Mapping protocol by changing iteration behavior and adding dict-like methods.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch comparison-mapping-ergonomics

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
tests/test_comparison.py (1)

230-233: Nit: docstring mentions "without warning" but the test doesn't assert it.

The docstring says items() returns pairs "without warning", but the test body only compares equality. If the no-warning behavior is part of the contract (vs. the prior implementation that may have emitted one), wrap the call in warnings.catch_warnings() with simplefilter('error'), or drop the "without warnings" phrase from the docstring to avoid misleading future readers.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/test_comparison.py` around lines 230 - 233, The test docstring claims
items() returns pairs "without warning" but the body doesn't assert that no
warnings occur; update the test_items function to enforce the no-warning
contract by calling comp.items() inside a warnings.catch_warnings() context and
set warnings.simplefilter('error') so any warning would fail the test (refer to
test_items, comp = fx.Comparison([...]), and the items() call on comp with
optimized_base and optimized_with_chp), or alternatively remove the "without
warning" phrase from the docstring if the no-warning behavior is not required.
flixopt/comparison.py (1)

239-254: Minor: keys()/values()/items() allocate a throwaway dict on every call.

Each invocation goes through self.flow_systems, which rebuilds a brand-new dict via dict(zip(self._names, self._systems, strict=True)). The returned view is only valid because that temporary dict is held alive by the view object itself — functionally fine, but you pay an O(n) dict construction for what should be cheap accessors, and successive calls return views backed by different dict instances (so identity comparisons between them won't hold).

Since _names and _systems are already the source of truth, you can avoid the allocation entirely — either by caching the dict or by returning lightweight iterables. If strict KeysView/ValuesView/ItemsView typing is important, cache on first access:

Proposed refactor
-    `@property`
-    def flow_systems(self) -> dict[str, FlowSystem]:
-        """Access underlying FlowSystems as a dict mapping name → FlowSystem."""
-        return dict(zip(self._names, self._systems, strict=True))
+    `@property`
+    def flow_systems(self) -> dict[str, FlowSystem]:
+        """Access underlying FlowSystems as a dict mapping name → FlowSystem."""
+        if self._flow_systems_cache is None:
+            self._flow_systems_cache = dict(zip(self._names, self._systems, strict=True))
+        return self._flow_systems_cache

(with a corresponding self._flow_systems_cache: dict[str, FlowSystem] | None = None in __init__).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@flixopt/comparison.py` around lines 239 - 254, The keys()/values()/items()
accessors currently call the flow_systems property which recreates a dict each
time via dict(zip(self._names, self._systems, strict=True)), causing O(n)
allocation on every call; change flow_systems to use a cached mapping (add
self._flow_systems_cache: dict[str, FlowSystem] | None = None in __init__) and
have flow_systems return the cached dict if present or build-and-store it once,
and ensure any mutating code that updates _names or _systems invalidates the
cache (set _flow_systems_cache = None) so the cached dict stays correct; this
preserves KeysView/ValuesView/ItemsView semantics while avoiding repeated
allocations.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@flixopt/comparison.py`:
- Around line 239-254: The keys()/values()/items() accessors currently call the
flow_systems property which recreates a dict each time via dict(zip(self._names,
self._systems, strict=True)), causing O(n) allocation on every call; change
flow_systems to use a cached mapping (add self._flow_systems_cache: dict[str,
FlowSystem] | None = None in __init__) and have flow_systems return the cached
dict if present or build-and-store it once, and ensure any mutating code that
updates _names or _systems invalidates the cache (set _flow_systems_cache =
None) so the cached dict stays correct; this preserves
KeysView/ValuesView/ItemsView semantics while avoiding repeated allocations.

In `@tests/test_comparison.py`:
- Around line 230-233: The test docstring claims items() returns pairs "without
warning" but the body doesn't assert that no warnings occur; update the
test_items function to enforce the no-warning contract by calling comp.items()
inside a warnings.catch_warnings() context and set
warnings.simplefilter('error') so any warning would fail the test (refer to
test_items, comp = fx.Comparison([...]), and the items() call on comp with
optimized_base and optimized_with_chp), or alternatively remove the "without
warning" phrase from the docstring if the no-warning behavior is not required.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 301c036a-80af-4e1a-b048-32b619709a21

📥 Commits

Reviewing files that changed from the base of the PR and between 5c55d36 and 633a59e.

📒 Files selected for processing (2)
  • flixopt/comparison.py
  • tests/test_comparison.py

@FBumann FBumann force-pushed the comparison-mapping-ergonomics branch 2 times, most recently from 10095b4 to 633a59e Compare April 20, 2026 09:25
FBumann and others added 2 commits April 20, 2026 11:32
linopy 0.6.x breaks with xarray 2026.3+ (TypeError on Dataset
constructor). Keep the release dependency range unchanged; only pin
for dev/CI until linopy ships a fix.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Raise TypeError if flow_systems is not a list, or if any item is not a
FlowSystem instance — replaces the cryptic AttributeError that would
otherwise surface later when attributes like .name or .solution are
accessed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
pyproject.toml (1)

80-80: Consider removing the xarray<2026.3 constraint — linopy v0.6.6 (latest) has no upper bound.

The code change is sound as-is and scoped to dev installs. However, linopy's latest release (v0.6.6, March 2026) already specifies xarray>=2024.2.0 with no upper bound, suggesting the xarray 2026.3+ compatibility fix has shipped. The TODO can likely be resolved and the constraint removed in a follow-up.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pyproject.toml` at line 80, The dependency pin "xarray<2026.3" in
pyproject.toml is now unnecessary because linopy v0.6.6 drops the upper bound;
remove the "<2026.3" upper bound from the xarray specification (i.e., replace
"xarray<2026.3" with simply "xarray" or "xarray>=<minimum-version>" as
appropriate) and delete the accompanying TODO comment so the dev install no
longer constrains xarray to <2026.3; update any related comment to reflect that
linopy v0.6.6 provides compatibility.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@flixopt/comparison.py`:
- Around line 163-171: The element-type validation for the Comparison input is
performed after the minimum-length check, causing a ValueError when a single
non-FlowSystem is passed; change the order so the non_fs check runs before the
len(flow_systems) < 2 check: move the creation/inspection of non_fs (the list
comprehension using isinstance(..., FlowSystem)) and the subsequent TypeError
raise to precede the length check on flow_systems, ensuring flow_systems and
FlowSystem are validated first and the correct TypeError is raised for invalid
elements.

---

Nitpick comments:
In `@pyproject.toml`:
- Line 80: The dependency pin "xarray<2026.3" in pyproject.toml is now
unnecessary because linopy v0.6.6 drops the upper bound; remove the "<2026.3"
upper bound from the xarray specification (i.e., replace "xarray<2026.3" with
simply "xarray" or "xarray>=<minimum-version>" as appropriate) and delete the
accompanying TODO comment so the dev install no longer constrains xarray to
<2026.3; update any related comment to reflect that linopy v0.6.6 provides
compatibility.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 202ece00-20ff-49e9-9cf3-5ffc1fdc50e3

📥 Commits

Reviewing files that changed from the base of the PR and between 633a59e and 18ec614.

📒 Files selected for processing (3)
  • flixopt/comparison.py
  • pyproject.toml
  • tests/test_comparison.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/test_comparison.py

Comment thread flixopt/comparison.py
Check that each element is a FlowSystem before the minimum-length
check, so `Comparison([not_a_flow_system])` surfaces the real problem
(wrong type) instead of "requires at least 2 FlowSystems".

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@FBumann FBumann merged commit 10103d2 into main Apr 20, 2026
5 of 9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant